package IC.SymbolTable;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import IC.TypeTable.MethodType;
import IC.TypeTable.Type;
import IC.TypeTable.TypeTable;

public class ClassSymbolTable extends SymbolTable 
{


	private Map<String,MethodSymbol> methodEntries = new LinkedHashMap<String, MethodSymbol>();
	private Map<String,FieldSymbol> fieldEntries = new LinkedHashMap<String, FieldSymbol>();
	private boolean hasSuperClass;


	// the parent Symbol Table of Class Table can be - 


	// 1. the Class Table of the super class
	public ClassSymbolTable(String id,ClassSymbolTable parent, GlobalSymbolTable global)
	{
		super(id, parent);
		this.hasSuperClass = true;
		this.stt = symbolTableType.CLASS;

	}

	// 2. the Global Table (in case there is no super class of this class)
	public ClassSymbolTable(String id ,GlobalSymbolTable parent)
	{
		super(id, parent);		
		this.hasSuperClass = false;
		this.stt = symbolTableType.CLASS;
	}


	public boolean hasSuperClass()
	{
		return this.hasSuperClass;
	}


	public MethodSymbol lookupMethodSymbolInCurrScope (String id)
	{
		if (methodEntries == null)
		{
			return null;			
		}

		return  methodEntries.get(id);
	}


	public MethodSymbol lookupMethodSymbol (String id)
	{
		MethodSymbol sym = methodEntries.get(id);

		if ((sym == null))
		{
			if (!hasSuperClass) 
			{ 
				return null;
			}

			else   
			{
				sym =  ((ClassSymbolTable) parentSymbolTable).lookupMethodSymbol(id);
			}

		}

		return sym;

	}


	public void addMethodSymbol(String name, Type returnType, List<Type> paramTypes, boolean isStatic)
	{
		this.methodEntries.put(name, new MethodSymbol(name,returnType,paramTypes, isStatic));
	}



	public void addMethodSymbol(String name, MethodSymbol ms)
	{

		this.methodEntries.put(name, ms);
	}



	public FieldSymbol lookupFieldSymbolInCurrScope (String id)
	{
		if (fieldEntries == null)
		{
			return null;			
		}

		return  fieldEntries.get(id);
	}


	public FieldSymbol lookupFieldSymbol (String id)
	{
		FieldSymbol sym = fieldEntries.get(id);

		if ((sym == null))
		{
			if (!hasSuperClass) 
			{ 
				return null;
			}

			else   
			{
				sym = (FieldSymbol) ((ClassSymbolTable) parentSymbolTable).lookupFieldSymbol(id);
			}

		}

		return sym;

	}


	public void addFieldSymbol(String name, String typeName,int line) throws SemanticError
	{
		this.fieldEntries.put(name,new FieldSymbol(name,typeName,line));
	}




	public ClassSymbol getSymbolOfCurrTableInGlobal() throws SemanticError
	{
		if (this.parentSymbolTable.stt == symbolTableType.GLOBAL)
		{
			return ((GlobalSymbolTable) parentSymbolTable).lookupClassSymbolInCurrScope(this.getId());
		}

		return ((ClassSymbolTable)this.parentSymbolTable).getSymbolOfCurrTableInGlobal();
	}



	// lookup for ClassSymbolTable - first in children, then in their children
	public ClassSymbolTable lookupClassSymbolTableRec(String name)
	{

		ClassSymbolTable csm = null;

		for (SymbolTable st : childrenSymbolTables)
		{ 
			if (st.getStt()==symbolTableType.CLASS)
			{
				if (st.getId().equals( name))
				{
					return (ClassSymbolTable)st; 

				}
			}
		}
		for (SymbolTable symt : childrenSymbolTables)
		{

			if (symt.getStt()==symbolTableType.CLASS)
			{
				csm = ((ClassSymbolTable)symt).lookupClassSymbolTableRec(name);
				if (csm != null)
				{
					break;
				}
			}
				

		}
		return csm;
	}











	public Symbol lookupSymbol(String id)
	{
		Symbol sym = lookupFieldSymbol(id);

		if (sym == null)
		{
			sym = lookupMethodSymbol(id);

			if (sym == null)
			{
				if (!hasSuperClass) 
				{ 
					return null;
				}

				else 
				{
					if (parentSymbolTable.stt == symbolTableType.CLASS)
					{
						sym = ((ClassSymbolTable) parentSymbolTable).lookupSymbol(id);
					}

				}
			}



		}


		return sym;
	}












	public boolean isDeclaredInThisOrSuperClass(String symId, char fieldOrMethodOrBoth)
	{
		if( isDeclaredInThisClass(symId,  fieldOrMethodOrBoth)  )
		{
			return true;
		}
		
		if (isDeclaredInSuperClass(symId, fieldOrMethodOrBoth))
		{
			return true;
		}

		
		if (this.hasSuperClass)
		{
			if (((ClassSymbolTable)parentSymbolTable).hasSuperClass)
			{
				return ((ClassSymbolTable)parentSymbolTable).isDeclaredInSuperClass(symId, fieldOrMethodOrBoth);
			}
		}
	

		
		return false;
	}


	public boolean isDeclaredInSuperClass(String symID, char fieldOrMethodOrBoth)
	{
		if (hasSuperClass) 

		{
			switch (fieldOrMethodOrBoth)
			{
			case 'F':
			case 'f': return ((ClassSymbolTable)parentSymbolTable).fieldEntries.containsKey(symID);
			case 'M':
			case 'm': return ((ClassSymbolTable)parentSymbolTable).methodEntries.containsKey(symID);
			case 'B':
			case 'b':  
				if ( (((ClassSymbolTable)parentSymbolTable).fieldEntries.containsKey(symID))
						|| (((ClassSymbolTable)parentSymbolTable).methodEntries.containsKey(symID)) )
				{
					return true;
				}

			}
			
			
		}

		return false;
	}


	public boolean isDeclaredInThisClass(String symID,  char fieldOrMethodOrBoth)
	{
		switch (fieldOrMethodOrBoth)
		{
		case 'F':
		case 'f': return fieldEntries.containsKey(symID);
		case 'M':
		case 'm': return methodEntries.containsKey(symID);
		case 'B':
		case 'b': 
			if ((fieldEntries.containsKey(symID)) || (methodEntries.containsKey(symID)))
			{
				return true;
			}

		}

		return false;
	}














	public Map<String, MethodSymbol> getMethodEntries() 
	{
		return methodEntries;
	}

	public Map<String, FieldSymbol> getFieldEntries() 
	{
		return fieldEntries;
	}
















	public String toString(){
		String str = "Class Symbol Table: "+this.getId();

		// print list of symbols (fields and methods)
		for(FieldSymbol fs: fieldEntries.values()){
			str += "\n\tField: "+fs.getType().getName()+" "+fs.getId();
		}

		for(MethodSymbol ms: methodEntries.values()){
			String mType = (ms.isStaticMethod() ?"Static":"Virtual")+" method";
			str += "\n\t"+mType+": "+ms.getId()+" "+ms.getType().toString();
		}

		// print list of children tables (classes and methods)

		String classChildren = "";
		String methodChildren = "";

		String classChildrenToString = "";
		String methodChildrenToString = "";

		if(!childrenSymbolTables.isEmpty()){
			str += "\nChildren tables: ";

			for(SymbolTable st: childrenSymbolTables)
			{
				if (st.stt == symbolTableType.METHOD)
				{
					methodChildren += st.getId() + ", ";
					methodChildrenToString += st.toString();
				}

				else if (st.stt == symbolTableType.CLASS)
				{
					classChildren += st.getId() + ", ";
					classChildrenToString += st.toString();
				}

			}

			str += methodChildren + classChildren;

			str = str.substring(0, str.length()-2);
		}
		str += "\n\n";


		// recursively print method children tables - first methods than classes

		str += methodChildrenToString + classChildrenToString;


		return str;
	}


}
